package nio;

import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 多端Nio聊天程序
 */
public class NioDemo5 {
    private static final Integer PORT = 8899;
    private static final Map<Integer, SocketChannel> CLIENT_MAP = new HashMap();
    private static final Map<SocketChannel, Integer> CLIENT_NAME_MAP = new HashMap();

    private static Integer count = 0;

    /**
     * 服务端
     * @throws Exception
     */
    private static void server() throws Exception {
        Selector selector = Selector.open();

        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        ServerSocket socket = serverSocketChannel.socket();
        InetSocketAddress address = new InetSocketAddress(PORT);
        socket.bind(address);

        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("监听端口：" + PORT);

        while (true){
            selector.select();
            Set<SelectionKey> selectionKeys = selector.selectedKeys();

            for (SelectionKey selectionKey : selectionKeys) {
                if (selectionKey.isAcceptable()) {
                    ServerSocketChannel channel = (ServerSocketChannel) selectionKey.channel();
                    SocketChannel client = channel.accept();
                    client.configureBlocking(false);
                    client.register(selector,SelectionKey.OP_READ);
                    CLIENT_MAP.put(count,client);
                    CLIENT_NAME_MAP.put(client,count);
                    System.out.println("接受client："+count);

                    count++;
                }else if (selectionKey.isReadable()){
                    SocketChannel client = (SocketChannel) selectionKey.channel();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(5);
                    int readNum = 0;
                    try {
                        readNum = client.read(byteBuffer);
                    }catch (IOException e){
                        Integer name = CLIENT_NAME_MAP.get(client);

                        //远程节点终端时移除
                        client.socket().close();
                        client.close();
                        CLIENT_MAP.remove(name);
                        CLIENT_NAME_MAP.remove(client);

                        System.out.println("name:"+name+"下线！");
                        continue;
                    }

                    if (readNum>0){
                        byteBuffer.flip();
                        String msg = String.valueOf(StandardCharsets.UTF_8.decode(byteBuffer).array());
                        System.out.println(client+":"+msg);
                        Integer sendName = CLIENT_NAME_MAP.get(client);

                        for (SocketChannel otherclient : CLIENT_MAP.values()) {
                            if (otherclient==client){
                                continue;
                            }
                            ByteBuffer writeBuffer = ByteBuffer.allocate(512);
                            writeBuffer.put((sendName.toString()+":"+msg).getBytes());
                            writeBuffer.flip();
                            otherclient.write(writeBuffer);
                        }
                    }

                }
            }

            selectionKeys.clear();
        }

    }

    private static void client() throws Exception{
        SocketChannel client = SocketChannel.open();
        client.configureBlocking(false);

        Selector selector = Selector.open();
        client.register(selector,SelectionKey.OP_CONNECT|SelectionKey.OP_READ);

        client.connect(new InetSocketAddress("127.0.0.1",8899));

        while (true){
            selector.select();
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            for (SelectionKey selectionKey : selectionKeys) {
                
                if (selectionKey.isConnectable()) {
                    SocketChannel channel = (SocketChannel) selectionKey.channel();

                    if (client.isConnectionPending()){
                        client.finishConnect();

                        ByteBuffer byteBuffer = ByteBuffer.allocate(512);

                        byteBuffer.put((LocalDateTime.now()+" 连接成功").getBytes());
                        byteBuffer.flip();
                        channel.write(byteBuffer);

                        ExecutorService executorService = Executors.newSingleThreadExecutor();

                        executorService.submit(()->{
                            while (true){
                                byteBuffer.clear();
                                Scanner scanner = new Scanner(System.in);
                                String msg = scanner.nextLine();

                                byteBuffer.put((msg+"\n").getBytes());
                                byteBuffer.flip();

                                channel.write(byteBuffer);
                            }
                        });
                    }
                }else if (selectionKey.isReadable()){
                    SocketChannel server = (SocketChannel) selectionKey.channel();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(512);

                    int readNum = server.read(byteBuffer);

                    if (readNum>0){
                        byteBuffer.flip();
                        String msg = String.valueOf(StandardCharsets.UTF_8.decode(byteBuffer).array());
                        System.out.println(msg);
                    }
                }
            }

            selectionKeys.clear();

        }
    }

    public static void main(String[] args) throws Exception {
        String arg = args[0];

        if (arg.equals("client")){
            System.out.println("启动客户端");
            client();
        }else if (arg.equals("server")){
            System.out.println("启动服务端");
            server();
        }
    }

}
